Skip to content

docs-gates: bring docs/adr/** and docs/audits/** into the doc-snippet walk, ledger-first (objectui#7856 card 2) - #8357

Merged
os-musk merged 1 commit into
mainfrom
claude/issue-7856-card2-adr-audits-snippet-walk
Sep 7, 2026
Merged

docs-gates: bring docs/adr/** and docs/audits/** into the doc-snippet walk, ledger-first (objectui#7856 card 2)#8357
os-musk merged 1 commit into
mainfrom
claude/issue-7856-card2-adr-audits-snippet-walk

Conversation

@os-musk

@os-musk os-musk commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7856

Card 2, the remainder the Release: line (5562488329) named and triage routed and shaped (5570217599). Card 1 landed as PR #8158 (9bfd6184) and is not reopened here.

Base fedfa3e4 · head 4e341a5b · four files · no edit inside docs/adr/** or docs/audits/**.

What lands

scripts/check-doc-snippet-types.mjs gains two legs, in the ROOT_DOCS pattern card 1 established — one exported constant plus one exported enumerator plus one listDocuments() call site each:

  • ADR_DOCS = { dir: 'docs/adr', recursive: true } + adrDocsPages()
  • AUDIT_DOCS = { dir: 'docs/audits', recursive: true } + auditDocsPages()

main() refuses a verdict (exit 2, "could not run") when either directory is missing from a real run, exactly as it does for ROOT_DOCS. The two are checked as a pair, because a rename that took only one of them out would leave the other's count looking healthy.

recursive: true here against ROOT_DOCS' false is the point rather than an inconsistency. Card 1's leg is non-recursive because its subdirectories were a different review route; these two legs are that route, so inside them there is nothing left to stop above, and a page filed under docs/adr/superseded/ tomorrow travels into the walk by itself. recursive is now read by the shared walk rather than being a comment on a constant.

Where the surface still stops is stated rather than left to be read off the collector: a subdirectory of docs/ that is neither of these two is in no leg. docs/screenshots/ is today's only other one and holds images, so the gap is currently empty — written down now rather than discovered later, and asserted in the pin.

Scan population 229 → 244.

LEDGER-FIRST — and it is the delivery, not a step toward one

Triage's ruling is a statement about the documents, not about cost:

docs/audits/** are dated audit snapshots — records of what was true on their date. ⇒ "Repairing" a code block inside one falsifies the record, exactly as it would inside an ADR.

So every page in the two subtrees that carries a ts/tsx block is named in UNGATED_DOCS with its measured count, and nothing inside either subtree is edited. What the widening buys is objectui#5174's distinction and only that: a document inside the walk and named on the ledger is a KNOWN debt with a number, where a document outside the walk is "neither covered NOR declared ungated" — invisible to the gate's own accounting, which is strictly worse. Paying it down is a per-record decision for whoever owns the record.

Census — measured on fedfa3e4, with the gate's own analyzer

Instrument: analyze({ ungated: {} }) for the population and compileSnippets() for the phases, against the closure --build-filter names (pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2 — 35/35 tasks successful). Not a hand count of fences.

record blocks fence lines phase diagnostics by code
docs/adr/0001-master-detail-subform.md 2 142, 211 syntax 15 TS1005 x5 · TS1109 x8 · TS1011 x1
docs/adr/0036-field-conditional-rules.md 1 178 syntax 3 TS1005 x2 · TS1109 x1
docs/adr/0057-console-ai-chat-one-conversation-docked.md 1 192 semantic 8 TS7006 x1 · TS7031 x3 · TS7053 x1 · TS2304 x3
docs/audits/2026-07-objectview-detailview-schema.md 1 66 syntax 3 TS1005 x3

Four records, five blocks, 29 diagnostics — 21 syntax-phase and 8 semantic — split 26 under docs/adr/** and 3 under docs/audits/**. The Release: line's 26/3 from 8507a2283 reproduces exactly on the current tree, docs/adr/0001 included at 15. The remaining eleven pages in the two subtrees carry no ts/tsx block: they are covered, contribute nothing, and may not be ledgered (the stale-ungated-entry check refuses an entry on a block-less page — proven in control A2 below).

⚠️ Read the syntax rows for what they do not say. A block that fails to parse never enters the semantic program, so its semantic half is not 0, it is UNMEASURED — and making such a block parse is the only way to learn what else is wrong with it. Each entry says so in its own words. docs/adr/0057 is the one block in either subtree that parses, so it is the only entry whose number is complete.

Per-block decision: ledger, no markers, no edit

All five blocks: UNGATED_DOCS entry, no fragment marker, no edit inside the record.

The marker was considered per Zone 1 item 3 and refused on a mechanical ground, not a stylistic one. An ungated document is never compiled at all (analyze filters covered = documents.filter((d) => !(d in ungated)) before any block is collected). A FRAGMENT_MARKER inside one of these records would therefore declare a block this gate already does not read — which is precisely the debt this file's own header names: "a marker on a block the gate no longer collects is debt nothing would ever fail to prompt the removal of". It would also be an edit inside a dated record, bought for nothing.

Stated the way the brief asks: a marker would not falsify the record's claims — it declares a block a fragment and changes no statement the record makes — but it is not needed, because the ledger entry alone already keeps the block accounted for. Where the brief offered "prefer NO edit inside the record at all and say so", that is the branch taken, and it is why this PR's diff touches no docs/ file.

A pin now holds that decision in place: no fragment marker was written inside either record subtree fails on the first marker anyone adds, so a later marker has to argue for itself.

Strictness region

Definition: the Fence scanning banner to EOF of scripts/check-doc-snippet-types.mjs.

  • before, on fedfa3e4: 2749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084b (unchanged from card 1's landed value, so the README batches had not moved it)
  • after, on 4e341a5b: 029535f916466b29820a2c6eaf7c8027cdf0c4621fa15997fc2e3c1b71f16c04

The scan population grew; no strictness rule moved. Proven by the region diff, which is 22 added lines and 1 replaced comment line, in exactly three places:

  1. the two call sites in listDocuments(), plus their comment;
  2. the missing-directory refusal in main();
  3. one comment line above the card-1 call site, which said the subtrees "cannot arrive here by accident" and now says they cannot arrive through that leg — they arrive through their own, immediately below.

Fence languages, the fragment-marker regex and its attachment walk, the verification-claim list, the ledger's re-derivation rules, the compiler options, the bound, the four controls and every diagnostic filter are byte-identical. The ledger itself (UNGATED_DOCS and its docblock) sits ABOVE the banner and is outside this region by construction.

Pins

  • scripts/__tests__/check-doc-snippet-types.test.ts — a new objectui#7856 card 2 block carrying the widening's own proof, in the shape this card admits rather than card 1's. Card 1 could prove its widening by showing its blocks in the COMPILED tier; card 2 may not make that claim and must not fake it, so the proof is the opposite shape and stricter: membership by name from the legs themselves; every block-bearing page in them is on the ledger; nothing from either leg reaches the compiled tier (asserted, not inferred — the day one is repaired, this test is what asks whether the record survived it); each leg entry carries a measured count, a diagnostic code, a phase and the word record, so "declared" cannot decay into an adjective; no marker inside either subtree; the recursion, the file/extension filter, the third-subdirectory stop, and the missing-directory refusal.
  • Card 1's stops at the top level test kept its leg assertion (rootDocsPages is unchanged and still the only place non-recursion is decided) and gained the honest half: the walk now collects the two named subtrees, and a third subdirectory (docs/rfcs/ in the fixture) is collected by nothing.
  • scripts/__tests__/check-doc-fence-languages.test.ts — the by-import subtraction extends with adrDocsPages() and auditDocsPages(); it was never a filename list, which is exactly why card 2 costs it three enumerators instead of a rewrite. Non-emptiness is now asserted per leg, because a union that is non-empty overall stays green with one member returning nothing. Its boundary test could no longer say "no nested page reaches either walk" — card 2 makes that false on purpose — so it says the stronger thing it still can: the snippet gate's nested pages are exactly the two subtree legs, and the fence guard's are still none.
  • scripts/__tests__/check-doc-expression-carriage.test.ts — run as a control, untouched, green.

Sibling gates check-doc-fence-languages and check-doc-component-types do not gain the legs.

Controls — by state, with on-disk proof and restore proven by blob sha

The brief's control (a) is inapplicable as written: no block in either subtree compiles, because ledger-first means all four records are ungated. Its fallback ("use stale-ungated-entry … an entry whose count is wrong must red") is falsified on the instrument — that check verifies existence and block-presence, never a count; a stale number in an entry fails nothing, which is why the ledger's own header refuses to write counts and why this PR's entries say their numbers are dated measurements. Three controls were run instead, each stronger than a count test.

Every leg: mutate → prove it reached disk by anchored grep -c and a changed git hash-object → run → restore under a trap … EXIT INT TERM with absolute paths → prove restore by git diff HEAD empty and the blob back to the HEAD blob 2e506a998a38dd56d6cb00656a77f0d5d476ae4f. All three ran against the committed tree.

A1 — the widening judges the records; only the ledger holds them out. Dropped the docs/adr/0057 entry (key hits 1 → 0, blob → af7a4d94). Gate EXIT=1, naming the record by file:line:col:

[semantic]  docs/adr/0057-console-ai-chat-one-conversation-docked.md:195:30  TS7006: Parameter 'surface' implicitly has an 'any' type.
[semantic]  docs/adr/0057-console-ai-chat-one-conversation-docked.md:198:35  TS2304: Cannot find name 'isBuildAgent'.

all 8 measured diagnostics, and the counters moved with it: 4 ungated → 3, 240 covered → 241, Semantic phase: 632 of 632 judged, 1 failed.

A2 — the ledger contract is live on these paths. Parked an entry on docs/adr/0034-unified-runtime-metadata-persistence.md, a leg page with no block (injected text hits 0 → 1, blob → 624992fd). Gate EXIT=1: docs/adr/0034-… [stale-ungated-entry] document holds no ts/tsx fenced block.

B — the silent skip is impossible. Removed the two call sites (call-site hits 1 → 0, blob → 49ad9b5c). Scanned 244 document(s)Scanned 229 document(s) — a drop of exactly 15, the pages the legs add (docs/adr 10 + docs/audits 5). The gate additionally EXIT=1 with four stale-ungated-entry — no such document in the scan set: the ledger cannot survive its legs being removed, so a future narrowing cannot be quiet.

Gates — hand-derived from .github/workflows/*.yml (objectui has no dispatch-gates derivation)

Exit codes captured by redirect before any pipe.

gate exit verdict line it printed
pnpm check:doc-snippets 0 Scanned 244 document(s): 240 covered (126 of them hold a ts/tsx block), 4 ungated · Covered blocks: 790 — 631 to compile, 159 declared fragment(s) · Semantic phase: 631 of 631 block(s) judged, 0 failed · Every covered documentation snippet compiles against the built types.
pnpm exec vitest run on the three sibling pins 0 Test Files 3 passed (3) · Tests 167 passed (167) (160 before this PR)
pnpm exec vitest run scripts/__tests__/ 0 Test Files 119 passed (119) · Tests 3544 passed (3544)
pnpm check:doc-fences 0 control: sibling unmoved
pnpm check:doc-types 0 control: sibling unmoved
pnpm check:doc-examples 0 control: sibling unmoved
pnpm type-check:scripts 0
pnpm lint:root 0 full run, no narrowing: 32 problems (0 errors, 32 warnings), all pre-existing
pnpm check:control-bytes 0 plus grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over all four touched files: no match
pnpm check:entry-guard 0
node scripts/check-doc-links.mjs 0
node scripts/check-changeset-presence.mjs --base fedfa3e4a 0 No source or published contract of a released package changed in this range, so no changeset is owed. — none written
node scripts/check-governed-queue-guard.mjs --self-test 0
node scripts/check-governed-queue-guard.mjs --test (4 touched paths) 0 NOT GOVERNED — 4 path(s) checked against 5 governed surface(s); none matched.
node scripts/check-governed-queue-guard.mjs --test docs/adr/0001-… 3 control leg, proving the instrument works: GOVERNED — docs/adr/** x1 — architecture decision records
git merge-tree --write-tree --name-only origin/main HEAD 0 re-fetched; origin/main at 289d1468, no overlap with this file surface

⚠️ The classification came out NOT GOVERNED, against the dispatch's expectation of exit 3 — precisely because ledger-first meant no edit inside docs/adr/**. The control leg above proves that is a real reading and not a broken instrument. This PR is nevertheless parked as a draft with no ready flip, no auto-merge, no enqueue, no review and no reviewers: the endgame is the PM seat's stroke, and the classification change is reported rather than acted on.

The judgement exercised most, on the four axes

Whether the four records get fragment markers as well as ledger entries (Zone 1 item 3's fork). A = ledger only, no edit inside either subtree (taken). B = ledger plus FRAGMENT_MARKER on the four non-parsing blocks. C = repair the blocks.

  • 实际业务需求. 实测判据,不是"读起来像有用":ungated 文档根本不进 covered,一个 marker 在这里没有任何读者 —— 门禁不读它,渲染器不显示它,ADR 的读者看不见它。B 的收益经实测为零(A1 控制反证了这一点:只有把 entry 拿掉,块才被判)。真实业务场景是"这些记录里的代码块从来没有仪器看过",A 用一个带数字的 debt 条目回答了它。
  • 项目长远合理性. contract-first、no workarounds:B 会在四份历史记录里各留一条只对某个未来状态有意义的声明,而这个仓库自己的头部已经把"门禁不再收集的块上的 marker"写成 debt。A 让"什么时候还债"留在记录所有者手里,不预先替他们改字节;C 直接违反裁决。
  • 防 AI 写代码犯错(尤其元数据). 这是本卡最重的一轴。三条路里只有 A 让下一个 agent 更难写错:它给出的机械信号是"这四页在 walk 里、在账上、一个块都没被编译",而 A2/A3 两条 pin 会在有人偷偷放宽时变红。B 的危险恰恰是消费端宽容那一类 —— 一个 marker 读起来像"这个块已经被处理过了",实际什么都没发生,正是"声明一个运行时不兑现的能力"。C 更糟:它会让 AI 学到"记录里的代码块可以为了让门禁变绿而改写",而那是可以静默伪造历史的形态。
  • 创业阶段不扩散需求. A 是三者中扩张面最小的:零字节进入记录、零新词汇、--build-filter 逐字节不变(没有块进入 compiled tier,所以这次加宽不给 CI 增加任何构建时间)。B 要新增四处声明面而无拉动;C 要一轮文档重写。过渡也从紧:A 不设分阶段窗口,它就是终态。

A,四轴一致,没有需要呈报的权衡。

验收备注

Noted, not filed — real teaching defects inside these records are proposals for the PM to open, never filed by this seat.

  • docs/adr/0057's resolver sketch names isBuildAgent, resolveAgentParam and resolveDefaultAgentName and defines none of them, and its parameters are unannotated. That is now measured (8 diagnostics) rather than suspected, but it is the record of what was agreed on 2026-07-13 — whether the shipped resolver still matches it is a question about the console, not about this ADR, and answering it belongs to whoever owns ADR-0057. Successor: the seat that next touches the console AI dock.
  • The three syntax-phase records teach schema shapes in prose TypeScript (?: on values, [...] elisions, 'create' | 'edit' where a value goes). A reader copying one gets something that does not parse. Whether that matters depends on whether an ADR is meant to be copied from — a question for the ADR owners, and deliberately not answered by a gate. Successor: none today; no open PR touches these files.
  • scripts/check-doc-fence-languages.mjs's header prose is stale in the same way objectui#8162 records for card 1 (it promises exact surface equality with this gate). Card 2 widens the divergence from one leg to three. objectui#8162 is already open and owns it; nothing new is filed. That header is outside this card's file surface and was not edited.

维护者速读(草稿)

改了什么

给文档代码块的类型门禁增加了两条扫描腿:docs/adr/**(架构决策记录)和 docs/audits/**(审计快照)。这两棵树以前没有任何门禁读过 —— 不是"读了放过",是根本不在扫描范围里,连"有多少债"都答不出来。现在它们在范围内了,每一页带 TypeScript 代码块的记录都以带实测数字的条目登记在账上:四份记录、五个代码块、29 个诊断(21 个连语法都过不去,8 个是语义)。没有改动这两棵树里的任何一个字节。

为什么改

triage 的裁定是关于文档性质的,不是关于成本:ADR 记的是"某一天决定了什么",审计记的是"某一天测到了什么"。把里面的代码块改到能编译,就是在改写历史记录。所以这次交付物就是这本账本身 —— 一棵没人点名的树是完全隐形的,一份带名字带数字的欠债是可管理的,后者严格地好。什么时候还这笔债,留给记录的所有者按记录逐份决定,不由一个门禁替他们定。

风险与代价(含回滚)

风险很低。CI 成本零增加:这两棵树没有一个块进入编译层,所以门禁要构建的包一个字节都没变。对其他两个文档门禁(check:doc-fencescheck:doc-types)没有任何影响,已作为对照跑绿。三条控制实验证明了这套机制真的在工作:把某条账目撤掉,门禁立刻按 文件:行 点名报错;把腿撤掉,扫描计数从 244 掉回 229、并且四条账目同时变红(所以以后没人能悄悄把它撤回去)。回滚就是 revert 这一个 commit,四个文件,不牵涉任何已发布的包,也不需要 changeset。

席位意见

(留空,待席位定稿)

你要做的

看一眼上面表格里那 29 个诊断的分布,确认「这两棵历史记录里的代码块暂时只登记、不修」是你要的处置 —— 如果是,这个 PR 就只等一次人工合并。


Generated by Claude Code

… walk, ledger-first

objectui#7856 card 2. Card 1 brought the top level of the root `docs/` tree
into `check-doc-snippet-types`' walk and stopped there deliberately: the two
subdirectories were a different review route. This lands them, as two exported
constants (`ADR_DOCS`, `AUDIT_DOCS`) plus one `listDocuments()` call site each,
in the `ROOT_DOCS` pattern card 1 established. `main()` refuses a verdict when
either directory is missing from a real run, exactly as it does for `docs/`.

LEDGER-FIRST, and that is the delivery rather than a step toward one. The
2026-09-07 triage ruling on the card is a statement about the documents, not
about cost: an ADR states what was decided on a date and an audit states what
was true on a date, so repairing a code block inside one falsifies the record.
Every page in the two subtrees that carries a ts/tsx block is therefore named
in `UNGATED_DOCS` with its measured count, and nothing inside either subtree
is edited.

Measured on `fedfa3e4` with the gate's own analyzer against the closure
`--build-filter` names: four records, five `ts` blocks, 29 diagnostics — 21
syntax-phase and 8 semantic — split 26 under `docs/adr/**` and 3 under
`docs/audits/**`. Four of the five blocks do not parse, so their semantic half
is unmeasured rather than clean, and each entry says so.

No fragment marker was written. An ungated document is never compiled, so a
marker inside one of these records would declare a block this gate already does
not read — debt with nothing to prompt its removal — while being an edit inside
a dated record. The ledger alone keeps the block accounted for.

The scan population moves 229 -> 244. Nothing from either subtree reaches the
compiled tier, so the build filter is byte-identical and this widening costs
the gate's job no build time. `check-doc-fence-languages` and
`check-doc-component-types` do not gain the legs; the walk-equality pin extends
its by-import subtraction with the two new enumerators rather than a filename
list.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants